namespace OFX.Aggregator.Implementation.BESnet.CreditCard
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Security.Cryptography;
	using System.Text;
	using System.Text.RegularExpressions;

	using Infrastructure;

	using Microsoft.VisualBasic;

	#endregion

	public sealed class StatementInterpreter : StatementInterpreterBase
	{
		public StatementInterpreter(string culture, IRegexPatternProvider regexProvider)
			: base(culture, regexProvider)
		{
		}

		public override Statement InterpretStatement(string html)
		{
			const RegexOptions options = RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.IgnoreCase;
			var regHeader = new Regex(this.regexProvider.GetMatchPattern("StatementHeader"), options);
			var matchStatementHeader = regHeader.Match(html);
			if (!matchStatementHeader.Success)
			{
				throw new InterpretationFailedException(this, "Screen scraping failed: Failed to find/understand statement header.");
			}

			string acctNumber = RetrieveMatchValue(matchStatementHeader, "acctNumber");
			string currency = RetrieveMatchValue(matchStatementHeader, "currency");
			string accountHolder = RetrieveMatchValue(matchStatementHeader, "accountHolder");
			double acctSpendingLimit = RetrieveMatchValueAsCurrency(matchStatementHeader, "acctSpendingLimit");

			var regHeader2 = new Regex(this.regexProvider.GetMatchPattern("StatementHeader2"), options);
			var matchStatementHeader2 = regHeader2.Match(html);
			if (!matchStatementHeader2.Success)
			{
				throw new InterpretationFailedException(this, "Screen scraping failed: Failed to find/understand secondary statement header.");
			}

			double balance = -RetrieveMatchValueAsCurrency(matchStatementHeader2, "balance");
			double previousBalance = -RetrieveMatchValueAsCurrency(matchStatementHeader2, "previousBalance");

			var statement = new Statement
			                	{
			                		Currency = currency,
			                		Date = DateTime.Now,
			                		Account =
			                			{
			                				Number = acctNumber,
			                				Type = AccountType.CreditCard,
			                				Holder = accountHolder,
			                				SpendingLimit = acctSpendingLimit,
			                				Balance = balance
			                			}
			                	};

			var regCardHeader = new Regex(this.regexProvider.GetMatchPattern("CardHeader"), options);

			var cardMatches = regCardHeader.Matches(html);
			if (cardMatches.Count == 0 && balance != previousBalance)
			{
				throw new InterpretationFailedException(this, "Screen scraping failed: Failed to find/understand statement card headers.");
			}
			foreach (Match cardMatch in cardMatches)
			{
				var creditCard = new PaymentOriginator
				                 	{
				                 		Id = RetrieveMatchValue(cardMatch, "creditCardAccountNumber")
				                 	};

				var lineRegex = new Regex(this.regexProvider.GetMatchPattern("StatementLine"), options);
				var statementLineMatches = lineRegex.Matches(cardMatch.Value);
				var linesInDateDictionary = new Dictionary<DateTime, int>();
				foreach (Match statementLineMatch in statementLineMatches)
				{
					var date = DateTime.Parse(RetrieveMatchValue(statementLineMatch, "date"), null, DateTimeStyles.AssumeLocal);
					date = date.AddHours(12);

					int occurrences;
					if (!linesInDateDictionary.TryGetValue(date.Date, out occurrences))
					{
						occurrences = 0;
					}

					string description = RetrieveMatchValue(statementLineMatch, "description");
					double comissions = -RetrieveMatchValueAsCurrency(statementLineMatch, "comissions");
					double amount = -RetrieveMatchValueAsCurrency(statementLineMatch, "amount");
					string originalCurrencyAmount = RetrieveMatchValue(statementLineMatch, "originalCurrencyAmount");
					string transactionId;
					if (occurrences++ == 0)
					{
						transactionId = GetMd5Hash(string.Format("{0}{1}{2}", date.ToUniversalTime().ToString("d", CultureInfo.InvariantCulture), creditCard.Id, amount));
					}
					else
					{
						transactionId =
							GetMd5Hash(string.Format("{0}{1}{2}_{3}", date.ToUniversalTime().ToString("d", CultureInfo.InvariantCulture), creditCard.Id, amount,
							                         occurrences));
					}

					linesInDateDictionary[date.Date] = occurrences;

					if (description.ToUpper() == description)
					{
						description = Strings.StrConv(description, VbStrConv.ProperCase, this.cultureInfo.LCID);
					}

					originalCurrencyAmount = originalCurrencyAmount.TrimEnd(new[] {'-'});

					var statementLine = new StatementLine
					                    	{
					                    		Amount = amount,
					                    		AdditionalCharges = comissions,
					                    		DatePosted = date,
					                    		DateValue = date,
					                    		Description = description,
					                    		Memo = originalCurrencyAmount,
					                    		OriginalCurrencyAmount = originalCurrencyAmount,
					                    		TransactionId = transactionId,
					                    		TransactionType = TransactionType.POS
					                    	};

					creditCard.StatementLines.Add(statementLine);
				}

				statement.PaymentOriginators.Add(creditCard);
			}

			StatementInterpreterHelper.InferStatementDates(statement);

			return statement;
		}

		private static string GetMd5Hash(string input)
		{
			// Create a new instance of the MD5CryptoServiceProvider object.
			var md5Hasher = MD5.Create();

			// Convert the input string to a byte array and compute the hash.
			var data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));

			// Create a new Stringbuilder to collect the bytes
			// and create a string.
			var sBuilder = new StringBuilder();

			// Loop through each byte of the hashed data 
			// and format each one as a hexadecimal string.
			foreach (byte ch in data)
			{
				sBuilder.Append(ch.ToString("x2"));
			}

			// Return the hexadecimal string.
			return sBuilder.ToString();
		}
	}
}